import { ConstantEnum } from '@libs/common/enums';
import {
  BaseRepository,
  indexBuilder,
  paginateBuilder,
} from '@libs/common/repository';
import { ApiQueryHandler } from '@libs/common/utils';
import { forwardRef, Inject, Injectable } from '@nestjs/common';
import { plainToInstance } from 'class-transformer';
import { DataSource, In, ObjectLiteral, SelectQueryBuilder } from 'typeorm';

import { PointTypeEnum } from '@/constant.enum';

import { Order } from '../order/entities/order.entity';
import { PointsService } from '../points/points.service';
import {
  AttendanceCreatePointsHistoryDto,
  ConsumePointsHistoryDto,
  FindPointsHistoryDto,
  FindUserPointsHistoryDto,
  IndexPointsHistoryDto,
  ManualPointsHistoryDto,
  RefundPointsHistoryDto,
  ShowPointsHistoryDto,
} from './dto/points-history.dto';
import { PointsHistory } from './entities/points-history.entity';

export function productFormatter(list: PointsHistory[]) {
  return list.map((v) => {
    return {
      ...v,
      order: undefined,
      productName: v?.order?.productName,
      productPrice: v?.order?.productPrice,
      productNumber: v?.order?.productNumber,
      productImage: v?.order?.productImage,
    };
  });
}

@Injectable()
export class PointsHistoryService extends BaseRepository {
  constructor(
    @Inject(ConstantEnum.DB_DATA_SOURCE_MANAGER) dataSource: DataSource,
  ) {
    super(dataSource);
  }

  protected repositoryManager = () => this.getRepository(PointsHistory);

  protected repository = this.getRepository(PointsHistory);

  // 解决相互引用问题
  @Inject(forwardRef(() => PointsService))
  private readonly pointsService: PointsService;

  private addLeftJoin<T extends SelectQueryBuilder<ObjectLiteral>>(q: T) {
    q.leftJoinAndMapOne(
      'PointsHistory.order',
      Order,
      'o',
      'o.orderNo = PointsHistory.orderNo',
    ).orderBy('PointsHistory.createdAt', 'DESC');

    return q;
  }

  async index(dto: IndexPointsHistoryDto) {
    let raw = indexBuilder({
      dto,
      repository: this.repository,
    });

    raw = this.addLeftJoin(raw);

    const res = await paginateBuilder(raw, dto);

    return res;
  }

  async show<D extends ShowPointsHistoryDto = ShowPointsHistoryDto>(dto: D) {
    const raw = this.repository
      .createQueryBuilder()
      .where('PointsHistory.id = :id', { id: dto.id });

    const res = await this.addLeftJoin(raw).getMany();

    if (!res) return null;

    if (dto.id) {
      return productFormatter(res)[0];
    }

    return productFormatter(res);
  }

  async attendanceCreate(dto: AttendanceCreatePointsHistoryDto) {
    if (dto.type !== PointTypeEnum['考勤']) {
      throw new Error('积分类型不正确');
    }
    if (!dto.dingId) {
      throw new Error('钉钉用户ID不能空');
    }

    // 存储数据库
    const entity = this.repositoryManager().create(
      plainToInstance(PointsHistory, dto),
    );
    await this.repositoryManager().insert(entity);

    const userPoints = await this.pointsService.count({
      userId: dto.userId,
      userName: dto.userName,
    });

    return { userPoints, id: entity.id };
  }

  async create(
    dto:
      | ManualPointsHistoryDto
      | ConsumePointsHistoryDto
      | RefundPointsHistoryDto,
  ) {
    if (dto.type === PointTypeEnum['消费']) {
      dto.points = Math.abs(dto.points) * -1;
    }

    if (dto.type === PointTypeEnum['手动']) {
      if (!dto.operatorId || !dto.operatorName) {
        throw new Error('操作人ID不能为空');
      }
    } else if (dto.type === PointTypeEnum['消费']) {
      if (!dto.orderNo) {
        throw new Error('订单号不能为空');
      }
    } else if (dto.type === PointTypeEnum['退款']) {
      if (!dto.orderNo) {
        throw new Error('订单号不能为空');
      }
      if (!dto.operatorId || !dto.operatorName) {
        throw new Error('操作人ID不能为空');
      }
    }

    // 存储数据库
    const entity = this.repositoryManager().create(
      plainToInstance(PointsHistory, dto),
    );

    await this.repositoryManager().insert(entity);

    // 更新用户的积分，异步处理防止报错
    this.pointsService.count({ userId: dto.userId, userName: dto?.userName });

    return entity;
  }

  async find(dto?: FindPointsHistoryDto) {
    return this.repository.find({
      where: dto || {},
    });
  }

  async findUserPointsHistory(dto: IndexPointsHistoryDto, userId: string) {
    const query = ApiQueryHandler(userId, 'userId', 'EQ');

    return this.index({ ...dto, query });
  }

  async findUserIds(dto: FindUserPointsHistoryDto) {
    return this.repository.find({
      select: ['userId'],
      where: {
        userId: In(dto.userId),
      },
    });
  }
}
